﻿/*
   Copyright 2012 Dmitry Bratus

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Transactions;
using System.Linq.Expressions;
using System.Threading.Tasks;

namespace Flower.Workflow
{
    /// <summary>
    /// The process builder interface.
    /// </summary>
    public interface IProcessBuilder
    {
        /// <summary>
        /// Opens a named scope.
        /// </summary>
        /// <param name="name">Name of the scope</param>
        /// <returns>This.</returns>
        IProcessBuilder Begin(string name);

        /// <summary>
        /// Opens a conditionally executed block.
        /// </summary>
        /// <param name="condition">The enter condition.</param>
        /// <returns>This.</returns>
        IProcessBuilder If(Expression<Func<IContext, bool>> condition);

        /// <summary>
        /// Opens an alternative block of a conditional execution.
        /// </summary>
        /// <returns>This.</returns>
        IProcessBuilder Else();

        /// <summary>
        /// Opens a conditionally executed alternative block.
        /// </summary>
        /// <param name="condition">The enter condition.</param>
        /// <returns>This.</returns>
        IProcessBuilder ElseIf(Expression<Func<IContext, bool>> condition);

        /// <summary>
        /// Opens a loop.
        /// </summary>
        /// <param name="condition">The exit condition.</param>
        /// <returns>This.</returns>
        IProcessBuilder While(Expression<Func<IContext, bool>> condition);

        /// <summary>
        /// Opens a loop.
        /// </summary>
        /// <param name="init">The action to be executed before the loop.</param>
        /// <param name="condition">The exit condition.</param>
        /// <param name="loop">The action to be executed after each iteration of the loop.</param>
        /// <returns>This.</returns>
        IProcessBuilder For(Action<IContext> init, Expression<Func<IContext, bool>> condition, Action<IContext> loop);

        /// <summary>
        /// Iterates messages in a subset.
        /// </summary>
        /// <typeparam name="T">The type of the message.</typeparam>
        /// <param name="subset">
        /// The path of the subset.
        /// 
        /// If the path starts with '/', it is considered a global path of a subset; 
        /// otherwise, it is considered a path of a local subset. In the later case the value 
        /// will be transformed to the global path '/Sets/Local/[pid]/[subset]'.
        /// </param>
        /// <param name="orderBy">Message property by which the messages are to be sorted.</param>
        /// <param name="order">The sort order.</param>
        /// <param name="batchSize">How many items to load in one round trip.</param>
        /// <param name="wait">Whether to wait for the messages if the subset is empty.</param>
        /// <param name="item">
        /// An action called on each iteration of the loop. Each next message is passed as argument
        /// to this action. The first argument is the message name, the second is the message data.
        /// </param>
        /// <returns>This.</returns>
        IProcessBuilder ForEach<T>
        (
            Expression<Func<IContext, string>> subset, 
            Expression<Func<IContext, MessageProperty>> orderBy, 
            Expression<Func<IContext, SortOrder>> order,
            Expression<Func<IContext, int>> batchSize,
            Expression<Func<IContext, bool>> wait, 
            Action<string, T> item 
        );

        /// <summary>
        /// Removes the current item in a ForEach loop from the iterated subset.
        /// </summary>
        /// <returns>This.</returns>
        IProcessBuilder RemoveCurrent();

        /// <summary>
        /// Updates the current item in a ForEach loop and saves it to the subset.
        /// </summary>
        /// <typeparam name="T">The type of the message.</typeparam>
        /// <param name="name">The new name of the message.</param>
        /// <param name="message">The new message data.</param>
        /// <param name="format">The format of the serialized message.</param>
        /// <returns>This.</returns>
        IProcessBuilder UpdateCurrent<T>(Expression<Func<IContext, string>> name, Expression<Func<IContext, T>> message, Expression<Func<IContext, MessageFormat>> format);

        /// <summary>
        /// Jumps out from the current loop.
        /// </summary>
        /// <returns>This.</returns>
        IProcessBuilder Break();

        /// <summary>
        /// Jumps to the next iteration of the current loop.
        /// </summary>
        /// <returns>This.</returns>
        IProcessBuilder Continue();

        /// <summary>
        /// Invokes a service by path.
        /// </summary>
        /// <typeparam name="TSvc">The service type.</typeparam>
        /// <param name="servicePath">
        /// The path of the service.
        /// 
        /// If the path starts with '/', it is considered a global path and not transformed;
        /// otherwise, it is prepended with '/Services/'.
        /// </param>
        /// <param name="call">The action performing the call.</param>
        /// <param name="flags">The activity execution flags.</param>
        /// <returns>This.</returns>
        IProcessBuilder Invoke<TSvc>(Expression<Func<IContext, string>> servicePath, Action<IContext, TSvc> call, ExecutionFlags flags = ExecutionFlags.None);

        /// <summary>
        /// Invokes an injected service.
        /// </summary>
        /// <typeparam name="TSvc">The service type.</typeparam>
        /// <param name="service">An instance of the service.</param>
        /// <param name="call">The action performing the call.</param>
        /// <param name="flags">The activity execution flags.</param>
        /// <returns>This.</returns>
        IProcessBuilder Invoke<TSvc>(TSvc service, Action<IContext, TSvc> call, ExecutionFlags flags = ExecutionFlags.None);

        /// <summary>
        /// Executes an arbitrary action.
        /// </summary>
        /// <param name="action">The action to execute.</param>
        /// <returns>This.</returns>
        IProcessBuilder Exec(Action<IContext> action);

        /// <summary>
        /// Executes an arbitrary asynchronous action.
        /// </summary>
        /// <param name="action">The action to execute (usually async method).</param>
        /// <returns>This.</returns>
        IProcessBuilder ExecAsync(Func<IContext, Task> action);

        /// <summary>
        /// Executes an arbitrary action and saves the current process state to the directory.
        /// </summary>
        /// <param name="action">The action to execute.</param>
        /// <returns>This.</returns>
        IProcessBuilder ExecAndSave(Action<IContext> action);

        /// <summary>
        /// Starts a process.
        /// 
        /// If the path starts with '/', it is considered a global path and not transformed;
        /// otherwise, it is prepended with '/Workflows/'.
        /// </summary>
        /// <typeparam name="TIn">The input message type.</typeparam>
        /// <typeparam name="TOut">The output message type.</typeparam>
        /// <param name="workflowPath">The path of the workflow defining the process.</param>
        /// <param name="arg">The input message of the initializer.</param>
        /// <param name="result">
        /// The action processing the result. The second argument of the action is the pid
        /// of the new process.
        /// </param>
        /// <returns>This.</returns>
        IProcessBuilder StartProcess<TIn, TOut>
        (
            Expression<Func<IContext, string>> workflowPath, 
            Expression<Func<IContext, TIn>> arg, 
            Action<IContext, string, TOut> result
        );

        /// <summary>
        /// Waits for a process to finish.
        /// </summary>
        /// <param name="pid">Pid of the process to wait.</param>
        /// <param name="waitingFinished">The action to perform when the waiting is finished.</param>
        /// <returns>This.</returns>
        IProcessBuilder JoinProcess(Expression<Func<IContext, string>> pid, Action<IContext> waitingFinished = null);

        /// <summary>
        /// Puts a message to a the default queue of the process.
        /// </summary>
        /// <typeparam name="TMsg">The type of the message.</typeparam>
        /// <param name="message">The message data.</param>
        /// <param name="format">The format of the serialized message.</param>
        /// <returns>This.</returns>
        IProcessBuilder Enqueue<TMsg>
        (
            Expression<Func<IContext, TMsg>> message,
            Expression<Func<IContext, MessageFormat>> format
        );

        /// <summary>
        /// Puts a message to a subset. Blocks if the set has limited capacity and it's full.
        /// </summary>
        /// <typeparam name="TMsg">The type of the message.</typeparam>
        /// <param name="subset">
        /// The path of the subset.
        /// 
        /// If the path starts with '/', it is considered a global path of a subset; 
        /// otherwise, it is considered a path of a local subset. In the later case the value 
        /// will be transformed to the global path '/Sets/Local/[pid]/[subset]'.
        /// </param>
        /// <param name="message">The message data.</param>
        /// <param name="format">The format of the serialized message.</param>
        /// <param name="waitingFinished">
        /// Optional. The action to perform when the waiting is finished.
        /// </param>
        /// <returns>This.</returns>
        IProcessBuilder Enqueue<TMsg>
        (
            Expression<Func<IContext, string>> subset, 
            Expression<Func<IContext, TMsg>> message,
            Expression<Func<IContext, MessageFormat>> format, 
            Action<IContext> waitingFinished = null
        );

        /// <summary>
        /// Puts a message to a subset. Blocks if the set has limited capacity and it's full.
        /// </summary>
        /// <typeparam name="TMsg">The type of the message.</typeparam>
        /// <param name="subset">
        /// The path of the subset.
        /// 
        /// If the path starts with '/', it is considered a global path of a subset; 
        /// otherwise, it is considered a path of a local subset. In the later case the value 
        /// will be transformed to the global path '/Sets/Local/[pid]/[subset]'.
        /// </param>
        /// <param name="messageName">The message name.</param>
        /// <param name="message">The message data.</param>
        /// <param name="format">The format of the serialized message.</param>
        /// <param name="waitingFinished">
        /// Optional. The action to perform when the waiting is finished.
        /// </param>
        /// <returns>This.</returns>
        IProcessBuilder Enqueue<TMsg>
        (
            Expression<Func<IContext, string>> subset, 
            Expression<Func<IContext, string>> messageName, 
            Expression<Func<IContext, TMsg>> message,
            Expression<Func<IContext, MessageFormat>> format, 
            Action<IContext> waitingFinished = null
        );

        /// <summary>
        /// Picks the next message in a subset, processes it and removes from the subset 
        /// in a single transaction.
        /// 
        /// The messages are picked in the default order i.e. the order close to that in which they
        /// were put to the subset.
        /// </summary>
        /// <typeparam name="TMsg">The type of a message.</typeparam>
        /// <param name="subset">
        /// The path of the subset.
        /// 
        /// If the path starts with '/', it is considered a global path of a subset; 
        /// otherwise, it is considered a path of a local subset. In the later case the value 
        /// will be transformed to the global path '/Sets/Local/[pid]/[subset]'.
        /// </param>
        /// <param name="message">The action processing the message.</param>
        /// <returns>This.</returns>
        IProcessBuilder Dequeue<TMsg>(Expression<Func<IContext, string>> subset, Action<IContext, TMsg> message);

        /// <summary>
        /// Picks the next message from the default queue of the process.
        /// 
        /// The messages are picked in the default order i.e. the order close to that in which they
        /// were put.
        /// </summary>
        /// <typeparam name="TMsg">The type of a message.</typeparam>
        /// <param name="message">The action processing the message.</param>
        /// <returns>This.</returns>
        IProcessBuilder Dequeue<TMsg>(Action<IContext, TMsg> message);

        /// <summary>
        /// Locks specified resources.
        /// </summary>
        /// <param name="resources">
        /// The resources to lock.
        /// 
        /// Each resource is a path of a subset with System.Int32 message type.
        /// 
        /// If the path starts with '/', it is considered a global path of a subset; 
        /// otherwise, it is considered a path of a local subset. In the later case the value 
        /// will be transformed to the global path '/Sets/Local/[pid]/[subset]'.
        /// </param>
        /// <param name="owner">An arbitrary string identifying the owner of the lock.</param>
        /// <param name="wait">Whether to until a resource is unlocked if it's locked by another owner.</param>
        /// <param name="result">
        /// Optional. The action to perform after the operation.
        /// 
        /// The second argument is the collection of failed locks. If this collection is empty, all
        /// the resources has been locked successfully. If the operation waits for unlocks, the locks
        /// never fail.
        /// </param>
        /// <returns>This.</returns>
        IProcessBuilder Lock
        (
            Expression<Func<IContext, IEnumerable<string>>> resources, 
            Expression<Func<IContext, string>> owner, 
            Expression<Func<IContext, bool>> wait, 
            Action<IContext, LockFailure[]> result = null
        );

        /// <summary>
        /// Unlocks specified resources.
        /// </summary>
        /// <param name="resources">
        /// The resources to unlock.
        /// 
        /// Each resource is a path of a subset with System.Int32 message type.
        /// 
        /// If the path starts with '/', it is considered a global path of a subset; 
        /// otherwise, it is considered a path of a local subset. In the later case the value 
        /// will be transformed to the global path '/Sets/Local/[pid]/[subset]'.
        /// </param>
        /// <param name="owner">An arbitrary string identifying the owner of the lock.</param>
        /// <returns>This.</returns>
        IProcessBuilder Unlock(Expression<Func<IContext, IEnumerable<string>>> resources, Expression<Func<IContext, string>> owner);

        /// <summary>
        /// Locks specified resources and opens a block within which
        /// the resources are locked. At the end of the block the resources
        /// are unlocked automatically.
        /// </summary>
        /// <param name="resources">
        /// The resources to lock.
        /// 
        /// Each resource is a path of a subset with System.Int32 message type.
        /// 
        /// If the path starts with '/', it is considered a global path of a subset; 
        /// otherwise, it is considered a path of a local subset. In the later case the value 
        /// will be transformed to the global path '/Sets/Local/[pid]/[subset]'.
        /// </param>
        /// <param name="owner">An arbitrary string identifying the owner of the lock.</param>
        /// <param name="wait">Whether to until a resource is unlocked if it's locked by another owner.</param>
        /// <param name="result">
        /// Optional. The action to perform after the operation.
        /// 
        /// The second argument is the collection of failed locks. If this collection is empty, all
        /// the resources has been locked successfully. If the operation waits for unlocks, the locks
        /// never fail.
        /// </param>
        /// <returns>This.</returns>
        IProcessBuilder BeginLock
        (
            Expression<Func<IContext, IEnumerable<string>>> resources, 
            Expression<Func<IContext, string>> owner, 
            Expression<Func<IContext, bool>> wait, 
            Action<IContext, LockFailure[]> result = null
        );

        /// <summary>
        /// Waits until a specified moment.
        /// </summary>
        /// <param name="moment">The moment to wait.</param>
        /// <param name="waitingFinished">
        /// Optional. The action to perform when the waiting is finished.
        /// </param>
        /// <returns>This.</returns>
        IProcessBuilder WaitUntil(Expression<Func<IContext, DateTime>> moment, Action<IContext> waitingFinished = null);

        /// <summary>
        /// Opens a block where various waiting statements 
        /// may be waited simultaneously.
        /// </summary>
        /// <returns>This.</returns>
        IProcessBuilder WaitAny();

        /// <summary>
        /// Opens an exception handling block.
        /// </summary>
        /// <returns>This.</returns>
        IProcessBuilder Try();

        /// <summary>
        /// Catches exceptions of a specified type.
        /// </summary>
        /// <typeparam name="TException">The type of the exceptions to handle.</typeparam>
        /// <param name="handler">The action to perform to handle an exception.</param>
        /// <returns>This.</returns>
        IProcessBuilder Catch<TException>(Action<IContext, TException> handler) where TException : Exception;

        /// <summary>
        /// Opens a block executed regardless of whether an exception was thrown in
        /// an exception handling block.
        /// </summary>
        /// <returns>This.</returns>
        IProcessBuilder Finally();

        /// <summary>
        /// Throws an exception.
        /// </summary>
        /// <typeparam name="TException">The type of the exception.</typeparam>
        /// <param name="exception">The exception to throw.</param>
        /// <returns>This.</returns>
        IProcessBuilder Throw<TException>(Expression<Func<IContext, TException>> exception) where TException : Exception;

        /// <summary>
        /// Closes a block.
        /// </summary>
        /// <returns>This.</returns>
        IProcessBuilder End();

        /// <summary>
        /// Assigns a name to the next activity.
        /// </summary>
        /// <param name="name">The name to assign.</param>
        /// <returns>This.</returns>
        IProcessBuilder Name(string name);

        /// <summary>
        /// Places a breakpoint.
        /// </summary>
        /// <param name="name">Name of the breakpoint.</param>
        /// <returns>This.</returns>
        IProcessBuilder Breakpoint(string name);

        /// <summary>
        /// Finishes the process.
        /// </summary>
        /// <returns>This.</returns>
        IProcessBuilder Finish();
    }
}
